package com.lkd.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.google.common.base.Strings;
import com.lkd.common.VMSystem;
import com.lkd.config.TopicConfig;
import com.lkd.contract.*;
import com.lkd.dao.TaskDao;
import com.lkd.emq.MqttProducer;
import com.lkd.entity.TaskDetailsEntity;
import com.lkd.entity.TaskEntity;
import com.lkd.exception.LogicException;
import com.lkd.feign.UserService;
import com.lkd.feign.VMService;
import com.lkd.http.vo.TaskDetailsViewModel;
import com.lkd.http.vo.TaskViewModel;
import com.lkd.service.TaskDetailsService;
import com.lkd.service.TaskService;
import com.lkd.utils.JsonUtil;
import com.lkd.vo.Pager;
import com.lkd.vo.UserVO;
import com.lkd.vo.VmVO;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.ArrayUtils;
import org.joda.time.LocalDateTime;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.IOException;
import java.text.SimpleDateFormat;
import java.time.Duration;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;

@Service
@Slf4j
public class TaskServiceImpl extends ServiceImpl<TaskDao, TaskEntity> implements TaskService {
    @Autowired
    private UserService userService;

    @Autowired
    private VMService vmService;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private TaskDetailsService taskDetailsService;

    @Autowired
    private MqttProducer mqttProducer;

    @Override
    public Pager<TaskEntity> search(Long pageIndex, Long pageSize, String innerCode, Integer userId, String taskCode, Integer status, Boolean isRepair, String start, String end) {
        Page<TaskEntity> page = new Page<>(pageIndex, pageSize);
        LambdaQueryWrapper<TaskEntity> qw = new LambdaQueryWrapper<>();
        if (!Strings.isNullOrEmpty(innerCode)) {
            qw.eq(TaskEntity::getInnerCode, innerCode);
        }
        if (userId != null && userId > 0) {
            qw.eq(TaskEntity::getUserId, userId);
        }
        if (!Strings.isNullOrEmpty(taskCode)) {
            qw.like(TaskEntity::getTaskCode, taskCode);
        }
        if (status != null && status > 0) {
            qw.eq(TaskEntity::getTaskStatus, status);
        }
        if (isRepair != null) {
            if (isRepair) {
                qw.ne(TaskEntity::getProductTypeId, VMSystem.TASK_TYPE_SUPPLY);
            } else {
                qw.eq(TaskEntity::getProductTypeId, VMSystem.TASK_TYPE_SUPPLY);
            }
        }
        if (!Strings.isNullOrEmpty(start) && !Strings.isNullOrEmpty(end)) {
            qw
                    .ge(TaskEntity::getCreateTime, LocalDate.parse(start, DateTimeFormatter.ISO_LOCAL_DATE))
                    .le(TaskEntity::getCreateTime, LocalDate.parse(end, DateTimeFormatter.ISO_LOCAL_DATE));
        }
        //根据最后更新时间倒序排序
        qw.orderByDesc(TaskEntity::getUpdateTime);

        return Pager.build(this.page(page, qw));
    }

    /**
     * 创建工单
     *
     * @param model 工单信息
     * @return
     */
    @Override
    @Transactional(rollbackFor = {Exception.class})
    public boolean create(TaskViewModel model) {
        // 1.检查参数
        UserVO user = userService.getUser(model.getUserId());
        VmVO vmInfo = vmService.getVMInfo(model.getInnerCode());
        check(model, user, vmInfo);

        // 校验同一台设备是否有未完成同类型工单
        checkTask(model.getInnerCode(), model.getProductType());

        // 2.设置参数
        TaskEntity task = new TaskEntity();
        // 2.1生成并设置工单编号
        String taskcode = createTaskCode();
        task.setTaskCode(taskcode);

        // 2.2拷贝属性
        BeanUtils.copyProperties(model, task);
        task.setTaskStatus(VMSystem.TASK_STATUS_CREATE);
        task.setUserId(model.getUserId());
        if (user != null) {
            task.setUserName(user.getUserName());
        }
        task.setRegionId(vmInfo.getRegionId());
        task.setProductTypeId(model.getProductType());
        task.setAddr(vmInfo.getNodeAddr());

        save(task);

        // 增加缓存中的员工工单数
        String day = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));
        String key = "";
        if (model.getProductType() == VMSystem.TASK_TYPE_SUPPLY) {
            key = VMSystem.REGION_TASK_KEY_PREF + day + VMSystem.USER_OPERATOR;
        } else {
            key = VMSystem.REGION_TASK_KEY_PREF + day + VMSystem.USER_REPAIRER;
        }
        redisTemplate.opsForZSet().incrementScore(key, task.getUserId(), 1);

        // 3.判断是否为补货工单
        if (model.getProductType() == VMSystem.TASK_TYPE_SUPPLY) {
            List<TaskDetailsViewModel> list = model.getDetails();
            if (CollectionUtils.isEmpty(list)) {
                throw new LogicException("补货信息为空");
            }

            // 检查参数,去重
            list = list.stream().distinct().collect(Collectors.toList());

            for (TaskDetailsViewModel taskDetailsViewModel : list) {
                TaskDetailsEntity taskDetailsEntity = new TaskDetailsEntity();
                taskDetailsEntity.setTaskId(task.getTaskId());
                BeanUtils.copyProperties(taskDetailsViewModel, taskDetailsEntity);
                taskDetailsService.save(taskDetailsEntity);
            }

        }

        return true;
    }

    /**
     * 完成工单
     *
     * @param taskId 工单id
     * @return
     */
    @Override
    public boolean completeTask(Long taskId) {
        // 查询工单
        LambdaQueryWrapper<TaskEntity> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(TaskEntity::getTaskId, taskId);
        TaskEntity taskEntity = getOne(queryWrapper);

        // 修改工单状态
        taskEntity.setTaskStatus(VMSystem.TASK_STATUS_FINISH);
        updateById(taskEntity);

        // 如果是补货工单
        if (taskEntity.getProductTypeId().equals(VMSystem.TASK_TYPE_SUPPLY)) {
            sendMsgSupply(taskEntity);
        } else {
            // 如果是运维工单
            sendMsgDeploy(taskEntity);
        }


        return true;
    }

    /**
     * 检查设备状态,自动创建维修工单
     *
     * @param vmStatusContract 设备状态信息
     * @return
     */
    @Override
    public void autoCreateRepairTask(VmStatusContract vmStatusContract) {
        // 获取设备编号
        String innerCode = vmStatusContract.getInnerCode();

        // 查询设备信息
        VmVO vmInfo = vmService.getVMInfo(innerCode);
        Integer userId = getUserId(vmInfo.getRegionId(),true);

        // 检查设备状态
        List<StatusInfo> statusInfoList = vmStatusContract.getStatusInfo();

        for (StatusInfo statusInfo : statusInfoList) {
            // 设备状态不正常则自动创建维修工单,指派给最少工单的运维人员
            if (!statusInfo.isStatus()) {

                // 封装创建工单参数
                TaskViewModel taskViewModel = new TaskViewModel();
                taskViewModel.setUserId(userId);
                taskViewModel.setCreateType(0);
                taskViewModel.setInnerCode(innerCode);
                taskViewModel.setProductType(VMSystem.TASK_TYPE_REPAIR);

                //创建运维工单
                this.create(taskViewModel);

            }
        }
    }

    /**
     * 获取一天内工单最少的用户
     * @param regionId 区域id
     * @param isRepair 是否是维修工单
     * @return
     */
    private Integer getUserId(Long regionId,boolean isRepair) {
        String roleCode = VMSystem.USER_OPERATOR;
        // 如果是维修工单
        if (isRepair){
            roleCode = VMSystem.USER_REPAIRER;
        }

        String day = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));
        // key = region.task.日期.区域id.角色编号
        String key = VMSystem.REGION_TASK_KEY_PREF + day + "." + regionId + "." + roleCode;

        // 获取用户id
        Set set = redisTemplate.opsForZSet().range(key, 0, 1);
        if (set.isEmpty() || set == null){
            throw new LogicException("该区域没有员工");
        }
        Integer userId = (Integer) Arrays.asList(set.toArray()).get(0);
        return userId;
    }

    /**
     * 自动创建补货工单
     *
     * @param contract
     */
    @Override
    public void autoCreateSupplyTask(SupplyContract contract) {
        List<SupplyChannel> supplyChannels = contract.getSupplyData();
        List<TaskDetailsViewModel> detailsViewModels = new ArrayList<>();
        String innerCode = contract.getInnerCode();

        // 查询设备信息
        VmVO vmInfo = vmService.getVMInfo(innerCode);
        Integer userId = getUserId(vmInfo.getRegionId(), false);

        // 属性拷贝
        for (SupplyChannel supplyChannel : supplyChannels) {
            TaskDetailsViewModel detailsViewModel = new TaskDetailsViewModel();
            BeanUtils.copyProperties(supplyChannel, detailsViewModel);
            detailsViewModel.setChannelCode(supplyChannel.getChannelId());
            detailsViewModel.setExpectCapacity(supplyChannel.getCapacity());
            detailsViewModels.add(detailsViewModel);
        }

        // 封装对象
        TaskViewModel model = new TaskViewModel();
        model.setDetails(detailsViewModels);
        model.setCreateType(0);
        model.setInnerCode(innerCode);
        model.setDesc("自动补货工单");
        model.setProductType(VMSystem.TASK_TYPE_SUPPLY);
        model.setUserId(userId);

        // 自动创建补货工单
        this.create(model);
    }

    /**
     * 发送补货工单的消息
     */
    private void sendMsgSupply(TaskEntity taskEntity) {

        // 查询补货工单信息
        LambdaQueryWrapper<TaskDetailsEntity> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(TaskDetailsEntity::getTaskId, taskEntity.getTaskId());
        List<TaskDetailsEntity> list = taskDetailsService.list(queryWrapper);

        // 数据拷贝
        List<SupplyChannel> supplyChannels = new ArrayList<>();
        for (TaskDetailsEntity detailsEntity : list) {
            SupplyChannel supplyChannel = new SupplyChannel();
            supplyChannel.setChannelId(detailsEntity.getChannelCode());
            supplyChannel.setCapacity(detailsEntity.getExpectCapacity());
            supplyChannels.add(supplyChannel);
        }

        // 设置补货工单协议
        SupplyContract contract = new SupplyContract();
        contract.setSupplyData(supplyChannels);
        contract.setInnerCode(taskEntity.getInnerCode());

        // 序列化
        String json = null;
        try {
            json = JsonUtil.serialize(contract);
        } catch (JsonProcessingException e) {
            log.error("发送消息异常");
        }

        // 发送补货工单消息
        mqttProducer.send(TopicConfig.TASK_SUPPLY_TOPIC, 2, json);
    }

    /**
     * 发送运维工单的消息
     *
     * @return
     */
    private void sendMsgDeploy(TaskEntity taskEntity) {
        // 设置完成运维工单协议
        TaskCompleteContract contract = new TaskCompleteContract();
        contract.setInnerCode(taskEntity.getInnerCode());
        contract.setTaskType(taskEntity.getProductTypeId());

        // 序列化完成运维工单协议
        String json = null;
        try {
            json = JsonUtil.serialize(contract);
        } catch (JsonProcessingException e) {
            log.error("发送消息异常");
        }

        // 发送运维工单消息
        mqttProducer.send(TopicConfig.VMS_COMPLETED_TOPIC, 2, json);

    }

    /**
     * 校验同一台设备是否有未完成同类型工单
     *
     * @param innerCode   设备编号
     * @param productType 工单类型
     */
    private void checkTask(String innerCode, int productType) {
        LambdaQueryWrapper<TaskEntity> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.select(TaskEntity::getTaskId)
                .eq(TaskEntity::getInnerCode, innerCode)
                .eq(TaskEntity::getProductTypeId, productType)
                .le(TaskEntity::getTaskStatus, VMSystem.TASK_STATUS_PROGRESS);
        int count = this.count(queryWrapper);
        if (count > 0) {
            throw new LogicException("当前设备有未完成的同类型工单");
        }
    }

    /**
     * 生成工单编号
     * 格式:yyyyMMddxxxx
     *
     * @return
     */
    private String createTaskCode() {
        // 1.获取当前时间yyyyMMdd
        String today = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));
        String key = "lkd.task.code" + today;
        // 2.将工单编码存入redis缓存
        Object obj = redisTemplate.opsForValue().get(key);
        if (obj == null) {
            redisTemplate.opsForValue().setIfAbsent(key, 0L, Duration.ofDays(1));
        }
        return today + String.format("%04d", redisTemplate.opsForValue().increment(key, 1));
    }

    /**
     * 检查参数
     *
     * @param model  传入参数
     * @param user   当前登录用户
     * @param vmInfo 售货机信息
     */
    private void check(TaskViewModel model, UserVO user, VmVO vmInfo) {
        if (model == null) {
            throw new LogicException("参数为空");
        }

        // 如果是手动创建工单
        if (model.getCreateType() == 1) {
            // 判断当前是否有登录用户
            if (user == null) {
                throw new LogicException("请先登录账号");
            }
        }

        if (vmInfo == null) {
            throw new LogicException("当前售货机不存在");
        }

        // 如果工单为投放工单,且设备状态为运营
        if (model.getProductType() == VMSystem.TASK_TYPE_DEPLOY && vmInfo.getVmStatus().equals(VMSystem.VM_STATUS_RUNNING)) {
            throw new LogicException("当前设备已运营");
        }
        // 如果工单为补货工单,且设备状态为未投放
        if (model.getProductType() == VMSystem.TASK_TYPE_SUPPLY && vmInfo.getVmStatus().equals(VMSystem.VM_STATUS_NODEPLOY)) {
            throw new LogicException("当前设备未运营");
        }
        // 如果工单为补货工单,且设备状态为撤机
        if (model.getProductType() == VMSystem.TASK_TYPE_SUPPLY && vmInfo.getVmStatus().equals(VMSystem.VM_STATUS_REVOKE)) {
            throw new LogicException("当前设备已撤机");
        }
    }

}
